package com.googlecode.delivery.core.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import com.googlecode.delivery.DeliveryException;

/**
 * NIO & Zip File Utils.
 * 
 * @author Stephen Kay
 * 
 */
public class FileUtils {
	private FileUtils() {
	}

	/**
	 * Recursive NIO file copy.
	 * 
	 * @param src
	 *            either a file or directory
	 * @param destDir
	 *            target directory
	 * @throws IOException
	 */
	public static void copy(File src, File destDir) throws IOException {
		if (src.isDirectory()) {
			if (src.getName().startsWith(".")) {
				return;
			}
			File[] files = src.listFiles();
			for (int i = 0; i < files.length; i++) {
				copy(files[i], new File(destDir.getAbsolutePath() + "/"
						+ src.getName()));
			}
		} else {
			if (!destDir.exists()) {
				// creates dest file
				destDir.mkdirs();
			} else if (!destDir.isDirectory()) {
				throw new IOException(destDir + " : not a directory");
			}
			File destFile = new File(destDir.getAbsolutePath() + "/"
					+ src.getName());
			destFile.createNewFile();
			copyFile(src, destFile);
		}
	}

	/**
	 * NIO copy file.
	 * 
	 * @param sourceFile
	 * @param destFile
	 * @throws IOException
	 */
	public static void copyFile(File sourceFile, File destFile)
			throws IOException {
		if (!destFile.exists()) {
			destFile.createNewFile();
		}

		FileChannel source = null;
		FileChannel destination = null;
		try {
			source = new FileInputStream(sourceFile).getChannel();
			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(source, 0, source.size());
		} finally {
			if (source != null) {
				source.close();
			}
			if (destination != null) {
				destination.close();
			}
		}
	}

	/**
	 * Creates given destFile writing given inputStream.
	 * 
	 * @param is
	 * @param size
	 * @param destFile
	 * @throws IOException
	 */
	private static void writeStream(InputStream is, long size, File destFile)
			throws IOException {
		if (!destFile.exists()) {
			destFile.createNewFile();
		}
		ReadableByteChannel rbc = Channels.newChannel(is);

		FileChannel destination = null;
		try {

			destination = new FileOutputStream(destFile).getChannel();
			destination.transferFrom(rbc, 0, size);
		} finally {
			if (rbc != null) {
				rbc.close();
			}
			if (destination != null) {
				destination.close();
			}
		}
	}

	/**
	 * @param zip
	 * @param destinationname
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public static void extract(ZipFile zip, File outputDir) throws IOException,
			DeliveryException {

		for (java.util.Enumeration e = zip.entries(); e.hasMoreElements();) {
			java.util.zip.ZipEntry entry = (java.util.zip.ZipEntry) e
					.nextElement();
			File file = new File(outputDir + "/" + entry.getName());
			if (entry.isDirectory()) {
				file.mkdirs();
			} else {
				String outputFile = outputDir.getAbsolutePath() + "/"
						+ entry.getName();

				// extracts at the output's root
				writeStream(zip.getInputStream(entry), entry.getSize(),
						new File(outputFile));
			}
		}
	}

	/**
	 * Zips given exploded dir into destZip file.
	 * 
	 * @param explodedDir
	 * @param destZip
	 * @throws IOException
	 */
	public static void zip(File explodedDir, File destZip) throws IOException {
		ZipOutputStream zos =new ZipOutputStream(new FileOutputStream(destZip));
		try {
			zip(explodedDir, explodedDir, "", zos);
		} finally {
			zos.close();
		}
	}

	/**
	 * Directory zip recusive method.
	 * 
	 * @param directory
	 * @param base
	 * @param entryPrefix
	 * @param zos
	 * @throws IOException
	 */
	private static void zip(File directory, File base, String entryPrefix,
			ZipOutputStream zos) throws IOException {
		File[] files = directory.listFiles();

		for (int i = 0, n = files.length; i < n; i++) {
			if (files[i].isDirectory()) {
				zip(files[i], base, entryPrefix, zos);
			} else {
				FileInputStream is = new FileInputStream(files[i]);
				ZipEntry entry = new ZipEntry(entryPrefix
						+ files[i].getPath().substring(
								base.getPath().length() + 1));
				addEntry(entry, is, zos);
			}
		}
	}

	private static void addEntry(ZipEntry entry, InputStream is,
			ZipOutputStream zos) throws IOException {
		byte[] buffer = new byte[8192];
		int read = 0;
		zos.putNextEntry(entry);
		try {
			while (-1 != (read = is.read(buffer))) {
				zos.write(buffer, 0, read);
			}
		} finally {
			zos.closeEntry();
			is.close();
		}
	}
}
